iOS正则表达式类 NSRegularExpression 详解

context

因目前手头的项目与不成熟的硬件工作,可能出现两个Json拼接到一起的情况,需要使用到正则表达式进行拆分.

现状

提到 iOS中的正则表达式 ,大家容易想到的有三种方式:

RangeOfString: option:

这种是最简单粗暴的方式,使用方法:

1
2
3
4
5
6
7
NSString *testString = @"{this is regex}";

NSRange range = [testString rangeOfString:@"[a-z]" options:NSRegularExpressionSearch];

if (range.location != NSNotFound) {
NSLog(@"%@", [testString substringWithRange:range]);
}

这种方式,简单并且容易理解. 但是对于我的案例, 我可能会有多个结果. 所以这个方式,不适合

NSPredicate

谓词匹配
Cocoa框架中的NSPredicate用于查询,原理和用法都类似于SQL中的where,作用相当于数据库的过滤取。关于谓词的更全面的知识, 在这里

在这里只讨论其作为正则表达式的用法:

1
2
3
4
NSString *emailString = @"guiqing1990@163.com";
NSString *regex = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
BOOL isValid = [predicate evaluateWithObject:emailString];

点击进入 NSPredicate 的头文件,可以看出, NSPredicate 绝大多数情况是用来做匹配验证的. 可以说,在作为正则表达式使用的情况下, NSPredicate 的功能是比 RangeOfString: option: 弱的.

NSRegularExpression

这是iOS自带的正则表达式类, 功能强大,几乎可以满足任何iOS开发中的正则需求.

初始化方法

1
2
+ (nullable NSRegularExpression *)regularExpressionWithPattern:(NSString *)pattern options:(NSRegularExpressionOptions)options error:(NSError **)error;
- (nullable instancetype)initWithPattern:(NSString *)pattern options:(NSRegularExpressionOptions)options error:(NSError **)error

实例初始化方法和对应的类方法 . 需要说明的是 options参数

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef NS_OPTIONS(NSUInteger, NSRegularExpressionOptions) {
NSRegularExpressionCaseInsensitive = 1 << 0, /* 大小写敏感 */
NSRegularExpressionAllowCommentsAndWhitespace = 1 << 1, /* 忽略空格或者 # - */
NSRegularExpressionIgnoreMetacharacters = 1 << 2, /* 把整个看成一个文本串 */
NSRegularExpressionDotMatchesLineSeparators = 1 << 3, /* 允许 . 匹配所有字符,包括行分隔符 */
NSRegularExpressionAnchorsMatchLines = 1 << 4, /* 允许^和$ 匹配行的开始和结束 */
NSRegularExpressionUseUnixLineSeparators = 1 << 5, /*
仅仅把 \n 当做行分隔符(否则,所有标准的行分隔符都算作行分隔符),查找范围为整个的话,无效
*/

NSRegularExpressionUseUnicodeWordBoundaries = 1 << 6 /*
使用TR#29作为单词边界(否则,传统的正则表达式的单词边界作为单词边界) 查找范围为整个的话,无效
*/

};

匹配方法

  1. 返回所有匹配结果的集合(适合,从一段字符串中提取我们想要匹配的所有数据)
1
- (NSArray *)matchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
  1. 返回正确匹配的个数(通过等于0,来验证邮箱,电话什么的,代替NSPredicate)
1
-(NSUInteger)numberOfMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
  1. 返回第一个匹配的结果。注意,匹配的结果保存在 NSTextCheckingResult 类型中
1
-(NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
  1. 返回第一个正确匹配结果字符串的NSRange
1
- (NSRange)rangeOfFirstMatchInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range;
  1. block方法
1
- (void)enumerateMatchesInString:(NSString *)string options:(NSMatchingOptions)options range:(NSRange)range usingBlock:(void (^)(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop))block;

在block中用到的枚举

下面的两个枚举用在block,其他地方,写0即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* enum {
NSMatchingReportProgress = 1 << 0,
NSMatchingReportCompletion = 1 << 1,
NSMatchingAnchored = 1 << 2,
NSMatchingWithTransparentBounds = 1 << 3,
NSMatchingWithoutAnchoringBounds = 1 << 4
};
typedef NSUInteger NSMatchingOptions;
*/


/** 此枚举值只在最后一个block方法中用到
* enum {
NSMatchingProgress = 1 << 0,
NSMatchingCompleted = 1 << 1,
NSMatchingHitEnd = 1 << 2,
NSMatchingRequiredEnd = 1 << 3,
NSMatchingInternalError = 1 << 4
};
typedef NSUInteger NSMatchingFlags;
*/

最后,附上我项目中的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NSString * rawString = @"{\"msg_id\":1793}{\"rval\":0,\"msg_id\":257,\"param\":9}";
NSError * error;
NSRegularExpression * regex = [NSRegularExpression regularExpressionWithPattern:@"\\{.[^{}]*msg_id.[^{}]*\\}" options:0 error:&error];

NSArray* array = [regex matchesInString:rawString options:0 range:NSMakeRange(0, [rawString length])];

NSMutableArray * resultArray = [NSMutableArray new];

[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSTextCheckingResult * result = (NSTextCheckingResult*)obj;
NSString* string = [rawString substringWithRange:result.range];
[resultArray addObject:string];
}];

return [resultArray copy];

注意点

最好在网页或者其他正则工具中,写好正则表达式,然后加入到代码中.可以省去反复调试的麻烦.

另外,记得要给特殊字符添加转义字符,否则会和正则本身的符号混淆,导致匹配不出结果

结语

学好正则表达式语法是非常值得的, 因为它在任何语言下都能发挥强大的作用 !